package com.ruge.seckill.service.impl;

import com.ruge.core.response.Response;
import com.ruge.core.tool.AssertTool;
import com.ruge.core.tool.RedisKeyTool;
import com.ruge.seckill.config.RedisLock;
import com.ruge.seckill.dao.UserFeign;
import com.ruge.seckill.domain.dao.SeckillCouponRepository;
import com.ruge.seckill.domain.dao.SeckillOrderRepository;
import com.ruge.seckill.domain.model.SeckillCoupon;
import com.ruge.seckill.domain.model.SeckillOrder;
import com.ruge.seckill.domain.request.SaveSeckillCouponRequest;
import com.ruge.seckill.error.SeckillError;
import com.ruge.seckill.service.SeckillCouponService;
import com.ruge.tool.id.IdTool;
import com.ruge.tool.json.JsonTool;
import com.ruge.tool.str.StringTool;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author ruge.wu
 * @since 2022/1/26 15:56
 */
@Service
public class SeckillCouponServiceImpl implements SeckillCouponService {

    @Resource
    private SeckillCouponRepository seckillCouponRepository;
    @Resource
    private SeckillOrderRepository seckillOrderRepository;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private DefaultRedisScript defaultRedisScript;
    @Resource
    private RedisLock redisLock;
    @Resource
    private RedissonClient redissonClient;

    @Resource
    private UserFeign userFeign;
    /**
     * 新增秒杀信息
     *
     * @param request {@link SaveSeckillCouponRequest}
     * @return {@link Response}
     */
    @Override
    public Response save(SaveSeckillCouponRequest request) {
        SeckillCoupon bean = JsonTool.getObjToBean(request, SeckillCoupon.class);
        bean.setStartTime(new Date(request.getStartTime()));
        bean.setEndTime(new Date(request.getEndTime()));

        /*===============mysql=================*/
//        验证数据库中是否已经存在该券的秒杀活动
//        SeckillCoupon seckillCoupon = seckillCouponRepository.findFirstByCouponIdAndIsValidTrue(request.getCouponId()).orElse(new SeckillCoupon());
//        AssertTool.mustTrue(StringTool.isBlank(seckillCoupon.getCouponId()), SeckillError.THE_COUPON_ALREADY_HAS_A_SNAP_UP_ACTIVITY);
//        seckillCouponRepository.save(bean);

        /*===============redis=================*/
        //验证在redis中是否已经存在该券的秒杀活动
        String secKillKey = RedisKeyTool.buildSecKillKey(request.getCouponId());
        Map<String, Object> seckillMaps = redisTemplate.opsForHash().entries(secKillKey);
        AssertTool.mustTrue(!StringTool.isNotBlank(seckillMaps), SeckillError.THE_COUPON_ALREADY_HAS_A_SNAP_UP_ACTIVITY);
        //同步数量到redis
        bean.setCreatedTime(new Date());
        bean.setUpdatedTime(new Date());
        redisTemplate.opsForHash().putAll(secKillKey, JsonTool.getObjToMap(bean));
        return Response.create();
    }

    /**
     * 抢购代金券
     *
     * @param couponId 优惠券id
     * @param aid
     * @return {@link Response}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response doSeckill(String couponId, Long aid) {

        Response userInfo = userFeign.getUserInfo(aid);
        if (StringTool.isNotBlank(userInfo)){
        return userInfo;
        }
//        mysql
//        判断此代金券是否加入抢购
//        SeckillCoupon seckillCoupon = seckillCouponRepository.findFirstByCouponId(couponId).orElseThrow(() -> new BaseBusinessException(SeckillError.THERE_IS_NO_RUSH_BUYING_ACTIVITY_FOR_COUPONS));
//        AssertTool.mustValid(seckillCoupon);

//        redis
//        判断此代金券是否加入抢购
        String secKillKey = RedisKeyTool.buildSecKillKey(couponId);
        Map<String, Object> seckillMaps = redisTemplate.opsForHash().entries(secKillKey);
        SeckillCoupon seckillCoupon = JsonTool.getObjToBean(seckillMaps, SeckillCoupon.class);

        // 判断是否开始、结束
        Date now = new Date();
        AssertTool.mustTrue(seckillCoupon.getStartTime().before(now), SeckillError.RUSH_PURCHASE_HAS_NOT_STARTED);
        AssertTool.mustTrue(seckillCoupon.getEndTime().after(now), SeckillError.THE_RUSH_PURCHASE_HAS_ENDED);
        // 判断是否卖完
        AssertTool.mustTrue(seckillCoupon.getAmount() >= 1, SeckillError.THE_COUPON_HAS_BEEN_SOLD_OUT);

//        mysql
        // 判断登录用户是否已抢到(一个用户针对这次活动只能买一次)
//        if (StringTool.isNotBlank(seckillOrderRepository.findFirstByUserId(aid).orElse(new SeckillOrder()).getOrderNo())) {
//            throw new BaseBusinessException(SeckillError.USERS_DO_NOT_NEED_TO_BUY_AGAIN);
//        }
//        redis
        // 判断登录用户是否已抢到(一个用户针对这次活动只能买一次)
        String lockName = RedisKeyTool.buildSingleSecKillKey(couponId, aid);
        long expireTime = seckillCoupon.getEndTime().getTime() - now.getTime();


        //lockKey 释放锁的时候比较使用  自定义 Redis 分布式锁
//        String lockKey = redisLock.tryLock(lockName, expireTime);
        // Redisson 分布式锁
        RLock lockKey = redissonClient.getLock(lockName);

//        mysql
//        扣库存
//        seckillCoupon.setAmount(seckillCoupon.getAmount() - 1);
//        seckillCouponRepository.updateAmountBySecKill(seckillCoupon.getCouponId());

//        redis
//        扣库存
//        long count = redisTemplate.opsForHash().increment(secKillKey, "amount", -1);
//        AssertTool.mustTrue(count < 0, SeckillError.THE_COUPON_HAS_BEEN_SOLD_OUT);

        try {
            //采用Redis +lua 扣库存 解决超卖
            List<String> keys = new ArrayList<>();
            keys.add(secKillKey);
            keys.add("amount");
            Long count = (Long) redisTemplate.execute(defaultRedisScript, keys);
            AssertTool.mustTrue(count > 0, SeckillError.THE_COUPON_HAS_BEEN_SOLD_OUT);


            SeckillOrder seckillOrder = new SeckillOrder();
            seckillOrder.setOrderNo(IdTool.getSnowflake().nextIdStr());
            seckillOrder.setCouponId(couponId);
            seckillOrder.setUserId(aid);
            seckillOrder.setOrderType(1);
            seckillOrder.setStatus(0);
            seckillOrder.setRemark(JsonTool.getObjToJson(seckillCoupon));
            seckillOrderRepository.save(seckillOrder);
        } catch (Exception e) {
            //手动回滚事物
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            //解锁
//            redisLock.unlock(lockName, lockKey);

            // Redisson 解锁
            lockKey.unlock();
            return Response.create(SeckillError.THE_COUPON_HAS_BEEN_SOLD_OUT);
        }

        return Response.create("抢购成功");
    }

    /**
     * 代金券列表
     *
     * @return {@link Response}
     */
    @Override
    public Response list() {
        List<SeckillCoupon> all = seckillCouponRepository.findAll();
        return Response.create(all);
    }
}
